﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using ExplorerService.Common;
using ExplorerService.Common.DataContracts;

namespace ExplorerService.Managers
{
    /// <summary>
    /// Singleton that monitors application sessions
    /// </summary>
    public class ApplicationSessionManager
    {
        private static ApplicationSessionManager _instance;
        private static readonly object Padlock = new object();
        private const int SessionTime = 3;

        ApplicationSessionManager()
        {
            _sessionExpiryWorker.DoWork += SessionExpiryWorkerDoWork;
            _sessionExpiryWorker.RunWorkerAsync();
        }

        /// <summary>
        /// Stores the application sessions
        /// </summary>
        private readonly List<ApplicationSession> _sessions = new List<ApplicationSession>();

        /// <summary>
        /// Worker used to monitor expired sessions
        /// </summary>
        private readonly BackgroundWorker _sessionExpiryWorker = new BackgroundWorker();

        /// <summary>
        /// Gets the application session for the specified sessionId
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns></returns>
        public ApplicationSession this[Guid sessionId]
        {
            get
            {
                return Sessions.FirstOrDefault(session => session.SessionId == sessionId);
            }

        }

        /// <summary>
        /// Gets the instance of the session manager
        /// </summary>
        public static ApplicationSessionManager Instance
        {
            get
            {
                lock (Padlock)
                {
                    return _instance ?? (_instance = new ApplicationSessionManager());
                }
            }
        }

        /// <summary>
        /// Gets the application sessions
        /// </summary>
        public List<ApplicationSession> Sessions
        {
            get { return _sessions; }
        }

        /// <summary>
        /// Monitors sessions and ends expired sessions
        /// </summary>
        static private void SessionExpiryWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                var time = DateTime.Now;
                var expiredSessions =
                    Instance.Sessions.Where(
                        session => session.SessionStarted && (session.SessionExpireTime < time)).ToList();
                foreach (var applicationSession in expiredSessions)
                {
                    Instance.EndSession(applicationSession.SessionId);
                }
                Thread.Sleep(500);
            }
        }

        /// <summary>
        /// Creates a new application session and returns the session ID
        /// </summary>
        /// <param name="applicationInformation">The application's information</param>
        /// <returns>Session Id</returns>
        public Guid GetNewApplicationSessionId(ApplicationInformation applicationInformation)
        {
            if (applicationInformation == null) return Guid.Empty;
            var newSession = new ApplicationSession
                                 {
                                     SessionId = Guid.NewGuid(),
                                     Information = applicationInformation,
                                     SessionStarted = false,
                                     SessionCreateTime = DateTime.Now,
                                     SessionStartTime = null,
                                     SessionExpireTime = DateTime.Now,
                                 };
            Sessions.Add(newSession);
            return newSession.SessionId;
        }


        /// <summary>
        /// Marks the application session as started
        /// </summary>
        public void StartSession(Guid sessionId)
        {
            var session = this[sessionId];
            if (session == null) return;
            session.SessionStarted = true;
            session.SessionStartTime = DateTime.Now;
            session.SessionExpireTime = DateTime.Now.AddSeconds(SessionTime);
            BroadcastSessionStart(sessionId);
        }

        /// <summary>
        /// Removes the application session which has the specified Id
        /// </summary>
        /// <param name="sessionId"></param>
        public void EndSession(Guid sessionId)
        {
            var session = this[sessionId];
            if (session == null) return;
            Sessions.Remove(session);
            BroadcastSessionEnd(sessionId);
        }



        /// <summary>
        /// Validates the session Id
        /// </summary>
        public bool ValidSessionId(Guid sessionId)
        {
            if (Instance[sessionId] == null) return false;
            Instance[sessionId].SessionExpireTime = DateTime.Now.AddSeconds(SessionTime);
            return true;
        }

        /// <summary>
        /// Checks if the session monitoring has started
        /// </summary>
        /// <param name="sessionId"></param>
        /// <returns>True if the session id is valid and the session monitoring has started</returns>
        public bool StartedSessionid(Guid sessionId)
        {
            var session = this[sessionId];
            if (session == null) return false;
            return session.SessionStartTime != null;
        }


        /// <summary>
        /// Performs the heartbeat for this session (extends session life)
        /// </summary>
        public bool HeartBeart(Guid sessionId)
        {
            var session = Instance[sessionId];
            if (session == null) throw new Exception("Invalid session ID");
            if (!session.SessionStarted) StartSession(sessionId);
            Thread.Sleep(200);
            session.SessionExpireTime = DateTime.Now.AddSeconds(SessionTime);
            return true;
        }



        /// <summary>
        /// Triggers the SessionEnded event
        /// </summary>
        private void BroadcastSessionEnd(Guid sessionId)
        {
            if (SessionEnded == null) return;
            SessionEnded(sessionId);
        }


        /// <summary>
        /// Triggers the SessionStarted event
        /// </summary>
        private void BroadcastSessionStart(Guid sessionId)
        {
            if (SessionStarted == null) return;
            SessionStarted(sessionId);
        }

        /// <summary>
        /// Triggers the SessionLocked event
        /// </summary>
        private void BroadcastSessionLocked(Guid sessionId, Guid clientSessionId)
        {
            if (SessionLocked == null) return;
            SessionLocked(sessionId, clientSessionId);
        }

        /// <summary>
        /// Triggers the SessionUnlocked event
        /// </summary>
        private void BroadcastSessionUnlocked(Guid sessionId)
        {
            if (SessionUnlocked == null) return;
            SessionUnlocked(sessionId);
        }


        /// <summary>
        /// Event triggered when a session has started. Not thread safe.
        /// </summary>
        public event SessionStartedHandler SessionStarted;
        public delegate void SessionStartedHandler(Guid sessionId);


        /// <summary>
        /// Event triggered when a session has ended. Not thread safe.
        /// </summary>
        public event SessionEndedHandler SessionEnded;
        public delegate void SessionEndedHandler(Guid sessionId);



        /// <summary>
        /// Event triggered when a session has been locked by a client.
        /// </summary>
        public event SessionLockedHandler SessionLocked;
        public delegate void SessionLockedHandler(Guid sessionId, Guid clientSessionId);

        /// <summary>
        /// Event triggered when a session has been unlocked
        /// </summary>
        public event SessionUnlockedHandler SessionUnlocked;
        public delegate void SessionUnlockedHandler(Guid sessionId);


        public void LockApplicationSession(Guid applicationSessionId, Guid clientSessionId)
        {
            if (!ValidSessionId(applicationSessionId)) return;
            Instance[applicationSessionId].Locked = true;
            if(SessionLocked!=null)
            {
                SessionLocked(applicationSessionId, clientSessionId);
            }

        }

        public void UnlockApplicationSession(Guid applicationSessionId)
        {
            if (!ValidSessionId(applicationSessionId)) return;
            Instance[applicationSessionId].Locked = false;
            if (SessionUnlocked != null)
            {
                SessionUnlocked(applicationSessionId);
            }
        }
    }
}
